// =============================================================================
//  Spangles Analyzer
//
//  Copyright(c) 2008
//  See LICENSE.txt for licensing information.
// =============================================================================

package com.google.spangles.domain;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import com.google.spangles.interfaces.impl.AbstractBounded;

/**
 * (Description) (Any invariants [conditions that are always true] ) (Usage
 * instructions, and/or examples) (TODOs, ASSUMPTIONs, etc.) (Known Bugs)
 * (Concurrency strategy - not thread safe or how it's thread safe)
 * 
 * @author Chris
 */
public class Position extends AbstractBounded
{
   Color toMove;

   Map<Location, Color> layout;

   private static final Translation SKEW_TRANSLATION = new Translation(-1, -1);

   private static final Translation OFFSET_TRANSLATION = new Translation(0, -2);

   private Symmetry[] group = new Symmetry[]
   {
            new Symmetry(0, 0), new Symmetry(0, 1), new Symmetry(0, 2),
            new Symmetry(0, 3), new Symmetry(0, 4), new Symmetry(0, 5),
            new Symmetry(1, 0), new Symmetry(1, 1), new Symmetry(1, 2),
            new Symmetry(1, 3), new Symmetry(1, 4), new Symmetry(1, 5)
   };

   public Position()
   {
      this.toMove = Color.WHITE;
      this.layout = new HashMap<Location, Color>();

      putPiece(Color.WHITE, new Location(0, 1));
      putPiece(Color.BLACK, new Location(0, 0));
   }

   /**
    * @param color
    *            The color to move.
    * @param pieces
    *            The current layout of pieces. Must not be NULL.
    */
   public Position(Color color, Map<Location, Color> pieces)
   {
      this.toMove = color;
      this.layout = pieces;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @return
    */
   public Color getToMove()
   {
      return toMove;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @param location
    * @return
    */
   public Color getPiece(Location l)
   {
      return layout.get(l);
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @param white
    * @param l
    */
   public Position putPiece(Color color, Location l)
   {
      layout.put(l, color);
      return this;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @param l
    */
   public Position playPiece(Location l)
   {
      Position q = putPiece(toMove, l);
      q.toMove = toMove.opponent();
      return q;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @return
    */
   public Map<Location, Color> getLayout()
   {
      return layout;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @return
    */
   public CanonicalPosition canonicalize()
   {
      // canonicalization of a position involves applying the right multiplies
      // of the skew and offset
      int x = getMinimumX();
      int y = getMinimumY();

      // first apply skew symmetry 0 to get x=0;
      int skews = x;
      x -= skews;
      y -= skews;

      // now apply translation symmetry 0 to get y=0 or 1
      int residue = (y & 1);
      y -= residue;
      int offsets = y / 2;

      Translation st = SKEW_TRANSLATION.scale(skews);
      Translation ot = OFFSET_TRANSLATION.scale(offsets);

      Map<Location, Color> l0 = getLayout();
      Map<Location, Color> l1 = new HashMap<Location, Color>();

      for (Iterator<Entry<Location, Color>> ei = l0.entrySet().iterator(); ei
                                                                             .hasNext();)
      {
         Entry<Location, Color> e = ei.next();
         Location l = e.getKey();
         Color c = e.getValue();

         l1.put(ot.apply(st.apply(l)), c);
      }

      return new CanonicalPosition(getToMove(), l1);
   }

   /*
    * (non-Javadoc)
    * 
    * @see java.lang.Object#hashCode()
    */
   public int hashCode()
   {
      final int prime = 31;
      int result = 1;
      result = prime * result + layout.hashCode();
      result = prime * result + toMove.hashCode();
      return result;
   }

   /*
    * (non-Javadoc)
    * 
    * @see java.lang.Object#equals(java.lang.Object)
    */
   public boolean equals(Object obj)
   {
      if (this == obj)
         return true;
      if (obj == null)
         return false;
      if (!(obj instanceof Position))
         return false;
      final Position other = (Position) obj;
      if (!layout.equals(other.layout))
         return false;
      if (toMove == null)
      {
         if (other.toMove != null)
            return false;
      }
      else if (!toMove.equals(other.toMove))
         return false;
      return true;
   }

   class ByEncoding implements Comparator<CanonicalPosition>
   {

      /*
       * (non-Javadoc)
       * 
       * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
       */
      public int compare(CanonicalPosition p1, CanonicalPosition p2)
      {
         byte[] a1 = p1.getEncoding();
         byte[] a2 = p2.getEncoding();

         if (a1.length != a2.length)
         {
            return a1.length - a2.length;
         }

         for (int i = 0; i < a1.length; i++)
         {
            int b1 = a1[i] & 0x00ff;
            int b2 = a2[i] & 0x00ff;
            if (b1 != b2)
            {
               return b1 - b2;
            }
         }
         return 0;
      }
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @return
    */
   public ReferencePosition referencize()
   {
      List<CanonicalPosition> symmetries = new LinkedList<CanonicalPosition>();

      for (int i = 0; i < group.length; i++)
      {
         Symmetry s = group[i];
         symmetries.add(s.apply(this));
      }
      Position best = Collections.min(symmetries, new ByEncoding());

      return new ReferencePosition(best.getToMove(), best.getLayout());
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @return
    */
   public Set<Location> getValidMoves()
   {
      Set<Location> output = new HashSet<Location>();

      Set<Location> current = layout.keySet();

      if (current.isEmpty())
      {
         output.add(new Location(0, 1));
      }
      else
      {
         for (Iterator<Location> ci = current.iterator(); ci.hasNext();)
         {
            Location c = ci.next();
            Collection<Location> n = c.getNeighbors();
            output.addAll(n);
         }
         output.removeAll(current);
      }
      return output;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @param location
    * @return
    */
   public Color checkWin(Location lastMove)
   {
      Color possibleWinner = null;

      Collection<Location> centers = lastMove.getNeighbors();
      centers.add(lastMove);

      Color currentPlayer = getPiece(lastMove);

      for (Iterator<Location> ci = centers.iterator(); ci.hasNext();)
      {
         Location c = ci.next();
         Color centerColor = getPiece(c);
         if (centerColor == null)
         {
            continue;
         }
         Color q = checkWinAt(c);
         if (currentPlayer.equals(q))
         {
            return q;
         }
         if (q != null)
         {
            possibleWinner = q;
         }
      }
      return possibleWinner;
   }

   /**
    * (Description - what the method does and why) (Visibility decision, if not
    * obvious) (Pre/Post Conditions) (Usage examples) (Algorithmic notes)
    * (Concurrency issues)
    * 
    * @param c
    * @return
    */
   private Color checkWinAt(Location c)
   {
      Collection<Location> neighbors = c.getNeighbors();
      Iterator<Location> ni = neighbors.iterator();

      Location n = ni.next();
      Color check = getPiece(n);

      if (check == null)
      {
         return null;
      }

      while (ni.hasNext())
      {
         n = ni.next();
         Color here = getPiece(n);
         if (!check.equals(here))
         {
            return null;
         }
      }

      return check;
   }

   public Collection<Location> locations()
   {
      return layout.keySet();
   }

   /**
    * (Description - what the method does and why)
    * (Visibility decision, if not obvious)
    * (Pre/Post Conditions)
    * (Usage examples)
    * (Algorithmic notes)
    * (Concurrency issues)
    * @return
    */
   public Position deadTileElimination()
   {
      Map<Location, Color> newLayout = new HashMap<Location, Color>();
      
      Set<Location> source = layout.keySet();
      
      for(Iterator<Location> li = source.iterator(); li.hasNext(); )
      {
         Location l = li.next();
         Color tileColor = layout.get(l);
         Collection<Location> centers = l.getNeighbors();
         boolean alive = false;
         
         // a location is dead if all neighboring supertriangles contain an opponent piece
         for(Iterator<Location> ci = centers.iterator(); (!alive) && ci.hasNext(); )
         {
            Location center = ci.next();
            Collection<Location> neighbors = center.getNeighbors();
            
            // this location is alive until I find an opponent
            boolean thisAlive = true;
            for(Iterator<Location> ni = neighbors.iterator(); (thisAlive) && ni.hasNext(); )
            {
               Location n = ni.next();
               Color nc = layout.get(n);
               if (tileColor.opponent().equals(nc))
               {
                  thisAlive = false;
               }
            }
            if (thisAlive)
            {
               alive = true;
            }            
         }
         
         if(alive)
         {
            newLayout.put(l, tileColor);
         }
         else
         {
            newLayout.put(l, Color.GRAY);
         }
      }
      
      return new Position(toMove, newLayout);
   }
}
